
/*
RFID Door Lock
10/13/2009
Brett Martin
www.pcmofo.com

Modified by Usama on 21st July 2010 for extra features.....

System for controlling access by reading 125khz RFID cards and compairing the ID tags to stored EEPROM values
Master RFID card used to program new cards into the system. 

ID-12/ID-12 RFID reader

Using sparkfun breakout board
[1] GND (Ground)
[2] /RST (Reset Reader) Connect to +5vDC 
[3] ANT (NC) 
[4] ANT (NC) 
[5] CP (NC) (Card Present) 
[6] NC
[7] FS (Format select) (ground this for ASCII)
[8] D1 (NC)
[9] D0 (TTL Serial) to arduino RX 
[10] Buzzer/LED (Card read indicator) (Connect to transistor + buzzer/led to indicate card was read)
[11] 5v+ ( +5vDC power) 

On Arduino....
[10] (Digital OUT) to Blue LED+
[11] (Digital OUT) to Red LED+
[12] (Digital OUT) to Green LED+
[13] (Digital OUT) to Alarm+
[9] (Digital OUT) to lock relay+ 
[RX0] (Serial IN) to [9] D0 TTL serial out on ID-204

// Based on code by BARRAGAN 
// and code from HC Gilje - http://hcgilje.wordpress.com/resources/rfid_id12_tagreader/
// Modified for Arudino by djmatic
// Modified for ID-12 and checksum by Martijn The - http://www.martijnthe.nl/
// Modified for Deleting cards and wiping memory and alarm by Usama.....
//
// Use the drawings from HC Gilje to wire up the ID-12.
// Remark: disconnect the rx serial wire to the ID-12 when uploading the sketch
*/

#define DEBUG

#pragma pack(0)
#include <EEPROM.h>
#include <SPI.h>
#include <stdarg.h>

#ifdef DEBUG
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <stdio.h>
#endif

#define CARD_DB (sizeof(struct config_block_header) + sizeof(struct config_block_v1))

//#define powerPin 10             // Blue LED
//#define failPin 9               // Red LED
//#define passPin 11              // Green LED
//#define doorPin 8               // Relay
//#define alarmPin 13             // Alarm

boolean programMode = false;    // Initialize program mode to false
boolean deleteMode = false;     // Initialize delete mode to false
boolean wipeMode = false;       // Initialize wipe mode to false
boolean match = false;          // initialize card match to false

byte storedCard[5];             // Stores an ID read from EEPROM
byte readCard[6];               // Sotres an ID read from the RFID reader
byte checksum = 0;              // Stores the checksum to verify the ID 

int alarm = 0;                  // Extra Security

struct config_block_v1
{
  byte powerPin;
  byte failPin;
  byte passPin;
  byte doorPin;
  byte alarmPin;
  byte mac[6];
};

struct config_block_header
{
  byte sig[4];
  byte version;
};

static struct config_block_v1 config;

#ifdef DEBUG
#define bug(...) do { printf(__VA_ARGS__); cli(); sleep_cpu(); } while(0)
#define log(...) do { printf(__VA_ARGS__); } while(0)

int serialWrite(char c, FILE *f) {
    Serial.write(c);
    return 0;
}

#else
#define bug(...) // nothing
#define log(...) // nothing
#endif /* DEBUG */

#define ENC28J60_CS_PIN 10
#define ENC28J60_INT_PIN 2
enum ENC28J60_opcode
{
  RCR = 0,
  RBM = 1,
  WCR = 2,
  WBM = 3,
  BFS = 4,
  BFC = 5,
  SRC = 7
};

byte ENC28J60_opcode_data[] =
{
  0, // RCR
  0, // RBM
  1, // WCR
  1, // WBM
  1, // BFS
  1, // BFC
  0, // None
  0  // SRC
};

enum ENC28J60_bank0_reg
{
  _ERDPTL = 0x00,
  _ERDPTH = 0x01,
  _EWRPTL = 0x02,
  _EWRPTH = 0x03,
  _ETXSTL = 0x04,
  _ETXSTH = 0x05,
  _ETXNDL = 0x06,
  _ETXNDH = 0x07,
  _ERXSTL = 0x08,
  _ERXSTH = 0x09,
  _ERXNDL = 0x0A,
  _ERXNDH = 0x0B,
  _ERXRDPTL = 0x0C,
  _ERXRDPTH = 0x0D,
  _ERXWRPTL = 0x0E,
  _ERXWRPTH = 0x0F,
  _EDMASTL = 0x10,
  _EDMASTH = 0x11,
  _EDMANDL = 0x12,
  _EDMANDH = 0x13,
  _EDMADSTL = 0x14,
  _EDMADSTH = 0x15,
  _EDMACSL = 0x16,
  _EDMACSH = 0x17
};

enum ENC28J60_bank1_reg
{
  _EHT0 = 0x00,
  _EHT1 = 0x01,
  _EHT2 = 0x02,
  _EHT3 = 0x03,
  _EHT4 = 0x04,
  _EHT5 = 0x05,
  _EHT6 = 0x06,
  _EHT7 = 0x07,
  _EPMM0 = 0x08,
  _EPMM1 = 0x09,
  _EPMM2 = 0x0A,
  _EPMM3 = 0x0B,
  _EPMM4 = 0x0C,
  _EPMM5 = 0x0D,
  _EPMM6 = 0x0E,
  _EPMM7 = 0x0F,
  _EPMCSL = 0x10,
  _EPMCSH = 0x11,
  _EPMOL = 0x14,
  _EPMOH = 0x15,
  _ERXFCON = 0x18,
  _EPKTCNT = 0x19
};

enum ENC28J60_bank2_reg
{
  _MACON1 = 0x00,
  _MACON2 = 0x01,
  _MACON3 = 0x02,
  _MACON4 = 0x03,
  _MABBIPG = 0x04,
  _MAIPGL = 0x06,
  _MAIPGH = 0x07,
  _MACLCON1 = 0x08,
  _MACLCON2 = 0x09,
  _MAMXFLL = 0x0A,
  _MAMXFLH = 0x0B,
  _MICMD = 0x12,
  _MIREGADR = 0x14,
  _MIWRL = 0x16,
  _MIWRH = 0x17,
  _MIRDL = 0x18,
  _MIRDH = 0x19
};

enum ENC28J60_bank3_reg
{
  _MAADR5 = 0x00,
  _MAADR6 = 0x01,
  _MAADR3 = 0x02,
  _MAADR4 = 0x03,
  _MAADR1 = 0x04,
  _MAADR2 = 0x05,
  _EBSTSD = 0x06,
  _EBSTCON = 0x07,
  _EBSTCSL = 0x08,
  _EBSTCSH = 0x09,
  _MISTAT = 0x0A,
  _EREVID = 0x12,
  _ECOCON = 0x15,
  _EFLOCON = 0x17,
  _EPAUSL = 0x18,
  _EPAUSH = 0x19
};

enum ENC28J60_common_reg
{
  _EIE = 0x1B,
  _EIR = 0x1C,
  _ESTAT = 0x1D,
  _ECON2 = 0x1E,
  _ECON1 = 0x1F
};

enum ENC28J60_phy_reg
{
  _PHCON1 = 0x0,
  _PHSTAT1 = 0x1,
  _PHID1 = 0x2,
  _PHID2 = 0x3,
  _PHCON2 = 0x10,
  _PHSTAT2 = 0x11,
  _PHIE = 0x12,
  _PHIR = 0x13,
  _PHLCON = 0x14
};

struct ENC28J60_cmd
{
  byte arg:5;
  enum ENC28J60_opcode opcode:3;
  byte data;
};

enum ENC28J60_bank
{
  BANK0 = 0,
  BANK1 = 1,
  BANK2 = 2,
  BANK3 = 3,
};

#define NON_ETH_REG 0x80

enum ENC28J60_reg
{
  ERDPTL = 0x00 | (BANK0 << 5),
  ERDPTH = 0x01 | (BANK0 << 5),
  EWRPTL = 0x02 | (BANK0 << 5),
  EWRPTH = 0x03 | (BANK0 << 5),
  ETXSTL = 0x04 | (BANK0 << 5),
  ETXSTH = 0x05 | (BANK0 << 5),
  ETXNDL = 0x06 | (BANK0 << 5),
  ETXNDH = 0x07 | (BANK0 << 5),
  ERXSTL = 0x08 | (BANK0 << 5),
  ERXSTH = 0x09 | (BANK0 << 5),
  ERXNDL = 0x0A | (BANK0 << 5),
  ERXNDH = 0x0B | (BANK0 << 5),
  ERXRDPTL = 0x0C | (BANK0 << 5),
  ERXRDPTH = 0x0D | (BANK0 << 5),
  ERXWRPTL = 0x0E | (BANK0 << 5),
  ERXWRPTH = 0x0F | (BANK0 << 5),
  EDMASTL = 0x10 | (BANK0 << 5),
  EDMASTH = 0x11 | (BANK0 << 5),
  EDMANDL = 0x12 | (BANK0 << 5),
  EDMANDH = 0x13 | (BANK0 << 5),
  EDMADSTL = 0x14 | (BANK0 << 5),
  EDMADSTH = 0x15 | (BANK0 << 5),
  EDMACSL = 0x16 | (BANK0 << 5),
  EDMACSH = 0x17 | (BANK0 << 5),
  EHT0 = 0x00 | (BANK1 << 5),
  EHT1 = 0x01 | (BANK1 << 5),
  EHT2 = 0x02 | (BANK1 << 5),
  EHT3 = 0x03 | (BANK1 << 5),
  EHT4 = 0x04 | (BANK1 << 5),
  EHT5 = 0x05 | (BANK1 << 5),
  EHT6 = 0x06 | (BANK1 << 5),
  EHT7 = 0x07 | (BANK1 << 5),
  EPMM0 = 0x08 | (BANK1 << 5),
  EPMM1 = 0x09 | (BANK1 << 5),
  EPMM2 = 0x0A | (BANK1 << 5),
  EPMM3 = 0x0B | (BANK1 << 5),
  EPMM4 = 0x0C | (BANK1 << 5),
  EPMM5 = 0x0D | (BANK1 << 5),
  EPMM6 = 0x0E | (BANK1 << 5),
  EPMM7 = 0x0F | (BANK1 << 5),
  EPMCSL = 0x10 | (BANK1 << 5),
  EPMCSH = 0x11 | (BANK1 << 5),
  EPMOL = 0x14 | (BANK1 << 5),
  EPMOH = 0x15 | (BANK1 << 5),
  ERXFCON = 0x18 | (BANK1 << 5),
  EPKTCNT = 0x19  | (BANK1 << 5),
  MACON1 = 0x00 | (BANK2 << 5) | NON_ETH_REG,
  MACON2 = 0x01 | (BANK2 << 5) | NON_ETH_REG,
  MACON3 = 0x02 | (BANK2 << 5) | NON_ETH_REG,
  MACON4 = 0x03 | (BANK2 << 5) | NON_ETH_REG,
  MABBIPG = 0x04 | (BANK2 << 5) | NON_ETH_REG,
  MAIPGL = 0x06 | (BANK2 << 5) | NON_ETH_REG,
  MAIPGH = 0x07 | (BANK2 << 5) | NON_ETH_REG,
  MACLCON1 = 0x08 | (BANK2 << 5) | NON_ETH_REG,
  MACLCON2 = 0x09 | (BANK2 << 5) | NON_ETH_REG,
  MAMXFLL = 0x0A | (BANK2 << 5) | NON_ETH_REG,
  MAMXFLH = 0x0B | (BANK2 << 5) | NON_ETH_REG,
  MICMD = 0x12 | (BANK2 << 5) | NON_ETH_REG,
  MIREGADR = 0x14 | (BANK2 << 5) | NON_ETH_REG,
  MIWRL = 0x16 | (BANK2 << 5) | NON_ETH_REG,
  MIWRH = 0x17 | (BANK2 << 5) | NON_ETH_REG,
  MIRDL = 0x18 | (BANK2 << 5) | NON_ETH_REG,
  MIRDH = 0x19 | (BANK2 << 5) | NON_ETH_REG,
  MAADR5 = 0x00 | (BANK3 << 5) | NON_ETH_REG,
  MAADR6 = 0x01 | (BANK3 << 5) | NON_ETH_REG,
  MAADR3 = 0x02 | (BANK3 << 5) | NON_ETH_REG,
  MAADR4 = 0x03 | (BANK3 << 5) | NON_ETH_REG,
  MAADR1 = 0x04 | (BANK3 << 5) | NON_ETH_REG,
  MAADR2 = 0x05 | (BANK3 << 5) | NON_ETH_REG,
  EBSTSD = 0x06 | (BANK3 << 5),
  EBSTCON = 0x07 | (BANK3 << 5),
  EBSTCSL = 0x08 | (BANK3 << 5),
  EBSTCSH = 0x09 | (BANK3 << 5),
  MISTAT = 0x0A | (BANK3 << 5) | NON_ETH_REG,
  EREVID = 0x12 | (BANK3 << 5),
  ECOCON = 0x15 | (BANK3 << 5),
  EFLOCON = 0x17 | (BANK3 << 5),
  EPAUSL = 0x18 | (BANK3 << 5),
  EPAUSH = 0x19 | (BANK3 << 5),
  EIE = 0x1B,
  EIR = 0x1C,
  ESTAT = 0x1D,
  ECON2 = 0x1E,
  ECON1 = 0x1F,
  PHCON1 = 0x0,
  PHSTAT1 = 0x1,
  PHID1 = 0x2,
  PHID2 = 0x3,
  PHCON2 = 0x10,
  PHSTAT2 = 0x11,
  PHIE = 0x12,
  PHIR = 0x13,
  PHLCON = 0x14
};

char * ENC28J60_revision_strs[] = 
{
  "?",  // 0
  "?",  // 1
  "B1", // 2
  "?",  // 3
  "B4", // 4
  "B5", // 5
  "B7"  // 6
};

struct ENC28J60_reg_id
{
  byte reg:5;
  enum ENC28J60_bank bank:2;
  byte non_eth_reg:1;
};

byte eth_cmd(byte opcode, byte arg, ...)
{
  ENC28J60_cmd cmd;
  byte rx;
  byte data;
  byte datalen;
  va_list va;
  static enum ENC28J60_bank bank = BANK0;
  struct ENC28J60_reg_id * reg = (struct ENC28J60_reg_id *) &arg;

  if(reg->reg < 0x1b && reg->bank != bank) {
    log("SPI: switching to bank %u\n", reg->bank);
    rx = eth_cmd(RCR, ECON1);
    rx &= ~3;
    rx |= reg->bank;
    eth_cmd(WCR, ECON1, rx);
    bank = reg->bank;
  }
  
  cmd.opcode = opcode;
  switch(opcode)
  {
    case RBM:
    case WBM:
      arg = 0x1a;
      break;
    case SRC:
      arg = 0x1f;
      break;
    default:
      cmd.arg = reg->reg;
  }

  datalen = ENC28J60_opcode_data[opcode];
  if(datalen) {
    va_start(va, arg);
    data = va_arg(va, int);
    va_end(va);
  }
  
  if(reg->non_eth_reg && (opcode == BFS || opcode == BFC)) {
    // The bit opcodes only work for ethernet registers, fake it for others.
    rx = eth_cmd(RCR, arg);
    if(opcode == BFS) {
      rx |= data;
    }
    if(opcode == BFC) {
      rx &= ~data;
    }
    eth_cmd(WCR, arg, rx);
    return 0;
  }
  
  digitalWrite(ENC28J60_CS_PIN, LOW);
  rx = SPI.transfer(((byte *)&cmd)[0]);
  log("SPI: %x -> %x", ((byte *)&cmd)[0], rx);
  if(datalen > 0) {
    rx = SPI.transfer(data);
    log(" %x -> %x", data, rx);
  }
  
  if(opcode == RCR) {
    rx = SPI.transfer(0);
    if(! reg->non_eth_reg) {
      // Don't log the dummy byte.
      log(" 0 -> %x", rx);    
    }
  }
  
  // Non-ethernet registers require an extra read cycle to get the real output byte.
  if(opcode == RCR && reg->non_eth_reg) {
    rx = SPI.transfer(0);
    log(" 0 -> %x", rx);
  }

  log("\n");
  
  digitalWrite(ENC28J60_CS_PIN, HIGH);
  
  return rx;
}

void eth_waitfor(byte reg, byte setbitmask, byte clearbitmask, byte millis)
{
  byte rx;
  do {
    rx = eth_cmd(RCR, reg); 
  } while((rx) && (rx));
}

void eth_int_handler(void)
{
  log("Bing\n");
}

void eth_self_test(void)
{
  byte rx;
  word edmacs;
  word ebstcs;
  log("Starting BIST\n");
  eth_cmd(WCR, ERXSTH, 0);
  eth_cmd(WCR, ERXSTL, 0);
  eth_cmd(WCR, EDMASTH, 0);
  eth_cmd(WCR, EDMASTL, 0);
  eth_cmd(WCR, EDMANDH, 0x1f);
  eth_cmd(WCR, EDMANDL, 0xff);
  eth_cmd(WCR, ERXNDH, 0x1f);
  eth_cmd(WCR, ERXNDL, 0xff);
  eth_cmd(WCR, EBSTSD, 0xaa); // Set the seed value for the PNRG
  eth_cmd(BFS, EBSTCON, 0x02); // Set EBSTCON.TME to enable test mode
  eth_cmd(BFS, EBSTCON, 0x01); // Set EBSTCON.BISTST to start the test
  do {
    rx = eth_cmd(RCR, EBSTCON);
  } while(rx & 1); // Wait until EBSTCON.BISTST clears.
  eth_cmd(BFC, EBSTCON, 0x02); // Clear EBSTCON.BISTST to stop the BIST unit and let the DMA controller go.
  eth_cmd(BFS, ECON1, 0x10); // Set ECON1.CSUMEN to enable checksumming by the DMA unit
  eth_cmd(BFS, ECON1, 0x20); // Set ECON1.DMAST to kick off the DMA controller.
  do {
    rx = eth_cmd(RCR, ECON1);
  } while(rx & 0x20); // Wait until ECON1.DMAST clears.

  log("EDMACS: %.02x%.02x\n", eth_cmd(RCR, EDMACSH), eth_cmd(RCR, EDMACSL));
  log("EBSTCS: %.02x%.02x\n", eth_cmd(RCR, EBSTCSH), eth_cmd(RCR, EBSTCSL));
}

void configEthernet(void)
{
  char mac[6];
  ENC28J60_cmd cmd;
  byte rx;
  word phyreg;
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  pinMode(ENC28J60_CS_PIN, OUTPUT);
  digitalWrite(ENC28J60_CS_PIN, HIGH);

  // Take the MAC out of reset.
  rx = eth_cmd(BFC, MACON2, 0x80);
  // Wait for the oscillator to stabilize. 
  while((eth_cmd(RCR, ESTAT) & 1) == 0);
  
  // Read a PHY register.
  rx = eth_cmd(WCR, MIREGADR, PHID2);
  eth_cmd(BFS, MICMD, 1);
  do {
    rx = eth_cmd(RCR, MISTAT); 
  } while(rx & 1); // BUSY
  
  do {
    rx = eth_cmd(RCR, MISTAT);
  } while(rx & 4); // NVALID
  eth_cmd(BFC, MICMD, 1);

  phyreg = eth_cmd(RCR, MIRDH, 0);
  phyreg <<= 8;
  phyreg |= eth_cmd(RCR, MIRDL, 0);  

  // Disable the clock output to save a little power.
  rx = eth_cmd(WCR, ECOCON, 0);
  rx = eth_cmd(RCR, EREVID, 0);
  log("ENC28J60 Revision %s\n", ENC28J60_revision_strs[rx]);
  log("PHID2: %.04x\n", phyreg);
  
  eth_self_test();
  attachInterrupt(0, eth_int_handler, RISING);
}

void setup()
{
    int i;
    struct config_block_header header;

    Serial.begin(9600);
#ifdef DEBUG
    stdout = fdevopen(serialWrite, NULL);
#endif

    //log("Sanity check: sizeof(header) = %d, sizeof(config) = %d\n", sizeof(struct config_block_header), sizeof(config));

    for(i = 0; i < sizeof(struct config_block_header); ++i) { 
      ((byte *)&header)[i] = EEPROM.read(i);
    }
    if(memcmp(header.sig, "SMRT", 4) != 0) {
      bug("Unprogrammed config block\n");
    }
    if(header.version != 1) {
      bug("Unrecognized config block version %d\n", header.version);
    }

    for(i = 0; i < sizeof(struct config_block_v1); ++i) {
      ((byte *)&config)[i] = EEPROM.read(sizeof(struct config_block_header) + i);
    }
    
    log("power: %d, pass: %d, fail: %d, door: %d, alarm: %d\n", config.powerPin, config.passPin, config.failPin, config.doorPin, config.alarmPin);
    
    pinMode(config.powerPin, OUTPUT);  // Connected to Blue on tri-color LED to indicate reader is ready
    pinMode(config.passPin, OUTPUT);   // Connected to Green on tri-color LED to indicate user is valid
    pinMode(config.failPin, OUTPUT);   // Connected to Red on tri-color LED to indicate user is NOT valid or read failed
    pinMode(config.doorPin, OUTPUT);   // Connected to relay to activate the door lock
    pinMode(config.alarmPin, OUTPUT);  // Connected to alarm
    alarm = 0;
    
    log("MAC address: %.02x:%.02x:%.02x:%.02x:%.02x:%.02x\n", config.mac[0], config.mac[1], config.mac[2],  config.mac[3],  config.mac[4],  config.mac[5]);
    configEthernet();
    log("Configuration done");
}

void loop()
{
    byte val = 0;               // Temp variable to hold the current byte
    normalModeOn();             // Normal mode, blue Power LED is on, all others are off
    if (alarm == 3) {
        digitalWrite(config.alarmPin, HIGH);   // Alarm!
        if (Serial.available() > 0)     // Waits for something to come on the serial line
        {
            if ((val = Serial.read()) == 2)     // First Byte should be 2, STX byte
            {
                getID();        // Get the ID, sets readCard = to the read ID
                if (isMaster(readCard)) // Check to see if it is the master programing card or delete card
                {
                    digitalWrite(config.alarmPin, LOW);
                    alarm = 0;
                    checksum = 0;

                } else
                    checksum = 0;

            }
        }
        checksum = 0;
    } else {

        if (programMode)        // Program mode to add a new ID card
        {
            programModeOn();    // Program Mode cycles through RGB waiting to read a new card
            if (Serial.available() > 0) // Waits for something to come on the serial line
            {
                if ((val = Serial.read()) == 2) // First Byte should be 2, STX byte
                {
                    getID();    // Get the ID, sets readCard = to the read ID
                    if (isMaster(readCard) || isDelete(readCard) || isWipe(readCard))   // Check to see if it is the master programing card or delete card
                    {
                        programMode = false;
                        if (isMaster(readCard))
                            openDoor(2);
                        else
                            failedWrite();
                        checksum = 0;
                    } else {
                        writeID(readCard);      // If not, write the card to the EEPROM sotrage
                        programMode = false;    // Turn off programing mode
                        checksum = 0;   // Make sure the checksum is empty
                    }
                }
            }
        } else if (deleteMode)  // Delete mode to delete an added ID card
        {
            deleteModeOn();     // Delete Mode cycles through RB waiting to read a new card
            if (Serial.available() > 0) // Waits for something to come on the serial line
            {
                if ((val = Serial.read()) == 2) // First Byte should be 2, STX byte
                {
                    getID();    // Get the ID, sets readCard = to the read ID
                    if (isMaster(readCard) || isDelete(readCard) || isWipe(readCard))   // Check to see if it is the master programing card or the Delete Card
                    {
                        deleteMode = false;
                        checksum = 0;
                        failedWrite();
                    } else      //if ( !isMaster(readCard) && !isDelete(readCard) )
                    {
                        deleteID(readCard);     // If not, delete the card from the EEPROM sotrage
                        deleteMode = false;     // Turn off delete mode
                        checksum = 0;   // Make sure the checksum is empty
                    }
                }
            }
        } else if (wipeMode)    // Wipe mode to wipe out the EEPROM
        {
            Serial.end();
            wipeModeOn();
            for (int i = CARD_DB; i < 512; i++)       // Loop repeats equal to the number of array in EEPROM
            {
                EEPROM.write(i, 0);
            }
            wipeMode = false;
            wipeModeOn();
            Serial.begin(9600);
        }
// Normal Operation...
        else {
            if (Serial.available() > 0) // If the serial port is available and sending data...
            {
                if ((val = Serial.read()) == 2) // First Byte should be 2, STX byte 
                {
                    getID();    // Get the ID, sets readCard = to the read ID
                    byte bytesread = 0;
                    log("Card read: ");
                    for (int i = 0; i < 5; i++) // Loop 5 times
                    {
                      log("%02x ", readCard[i]);
                    }
                    log("\n");
                    log("Checksum: %02x\n", readCard[5]);
                    if (readCard[5] == checksum)        // See if the 5th BYTE (the checksum) read in from the reader
                    {           // matches the checksum caculated 
                        checksum = 0;   // If so, we can empty the variable storing the calculated checksum
//Serial.println(" passed"); 
//Serial.println();
                        if (isMaster(readCard)) // Check to see if the card is the master programing card
                        {
                            programMode = true; // If so, enable programing mode
                            alarm = 0;
                        } else if (isDelete(readCard))  // Check to see if the card is the deletion card
                        {
                            deleteMode = true;  // If so, enable deletion mode
                            alarm = 0;
                        } else if (isWipe(readCard))    // Check to see if the card is the deletion card
                        {
                            wipeMode = true;    // If so, enable deletion mode
                            alarm = 0;
                        } else {
                            if (findID(readCard))       // If not, see if the card is in the EEPROM
                            {
                                openDoor(2);    // If it is, open the door lock
                                alarm = 0;
                            } else {
                                failed();       // If not, show that the ID was not valid
                                alarm++;
                            }
                        }
                    } else      // If the checksum failed
                    {           // Print out the checksum
/*
Serial.println(" error");
Serial.println();
Serial.print("[");
Serial.print(readCard[5], HEX);
Serial.print("] != [");
Serial.print(checksum, HEX);
Serial.print("] ");
*/
                    }
                }
            }
        }
    }
}

// If the serial port is ready and we received the STX BYTE (2) then this function is called 
// to get the 4 BYTE ID + 1 BYTE checksum. The ID+checksum is stored in readCard[6]
// Bytes 0-4 are the 5 ID bytes, byte 5 is the checksum
void getID()
{
    byte bytesread = 0;
    byte i = 0;
    byte val = 0;
    byte tempbyte = 0;
// 5 HEX Byte code is actually 10 ASCII Bytes. 
    while (bytesread < 12)      // Read 10 digit code + 2 digit checksum
    {
        if (Serial.available() > 0)     // Check to make sure data is coming on the serial line
        {
            val = Serial.read();        // Store the current ASCII byte in val
            if ((val == 0x0D) || (val == 0x0A) || (val == 0x03) || (val == 0x02)) {     // If header or stop bytes before the 10 digit reading
                break;          // Stop reading 
            }
            if ((val >= '0') && (val <= '9'))   // Do Ascii/Hex conversion
            {
                val = val - '0';
            } else if ((val >= 'A') && (val <= 'F')) {
                val = 10 + val - 'A';
            }
            if (bytesread & 1 == 1)     // Every two ASCII charactors = 1 BYTE in HEX format
            {
// Make some space for this hex-digit by
// shifting the previous hex-digit with 4 bits to the left:
                readCard[bytesread >> 1] = (val | (tempbyte << 4));
                if (bytesread >> 1 != 5)        // If we're at the checksum byte,
                {
                    checksum ^= readCard[bytesread >> 1];       // Calculate the checksum using XOR
                };
            } else              // If it is the first HEX charactor
            {
                tempbyte = val; // Store the HEX in a temp variable
            };
            bytesread++;        // Increment the counter to keep track
        }
    }
    bytesread = 0;
}

// Read an ID from EEPROM and save it to the storedCard[6] array
void readID(int number)         // Number = position in EEPROM to get the 5 Bytes from 
{
    int start = CARD_DB +(number * 5) - 4;       // Figure out starting position
//Serial.print("Start: ");
//Serial.print(start);
//Serial.print("\n\n");
    for (int i = 0; i < 5; i++) // Loop 5 times to get the 5 Bytes
    {
        storedCard[i] = EEPROM.read(start + i); // Assign values read from EEPROM to array
/*
Serial.print("Read [");
Serial.print(start+i);
Serial.print("] [");
Serial.print(storedCard[i], HEX);
Serial.print("] \n");
*/
    }
}

// Write an array to the EEPROM in the next available slot
void writeID(byte a[])
{
    if (!findID(a))             // Before we write to the EEPROM, check to see if we have seen this card before!
    {
        int num = EEPROM.read(CARD_DB);       // Get the numer of used spaces, position 0 stores the number of ID cards
/*
Serial.print("Num: ");
Serial.print(num);
Serial.print(" \n");
*/
        int start = CARD_DB + (num * 5) + 1;      // Figure out where the next slot starts
        num++;                  // Increment the counter by one
        EEPROM.write(CARD_DB, num);   // Write the new count to the counter
        for (int j = 0; j < 5; j++)     // Loop 5 times
        {
            EEPROM.write(start + j, a[j]);      // Write the array values to EEPROM in the right position
/*
Serial.print("W[");
Serial.print(start+j);
Serial.print("] Value [");
Serial.print(a[j], HEX);
Serial.print("] \n");
*/
        }
        successWrite();
    } else {
        failedWrite();
    }
}

// Delete an array stored in EEPROM from the designated slot
void deleteID(byte a[])
{
    if (!findID(a))             // Before we delete from the EEPROM, check to see if we have this card!
    {
        failedWrite();          // If not
    } else {
        int num = EEPROM.read(CARD_DB);       // Get the numer of used spaces, position 0 stores the number of ID cards
        int slot;               // Figure out the slot number of the card
        int start;              // = ( num * 5 ) + 1; // Figure out where the next slot starts
        int looping;            // The number of times the loop repeats
        int j;

        int count = EEPROM.read(CARD_DB);     // Read the first Byte of EEPROM that
// Serial.print("Count: "); // stores the number of ID's in EEPROM
// Serial.print(count);
//Serial.print("\n");
        slot = findIDSLOT(a);   //Figure out the slot number of the card to delete
        start = CARD_DB + (slot * 5) - 4;
        looping = ((num - slot) * 5);
        num--;                  // Decrement the counter by one
        EEPROM.write(CARD_DB, num);   // Write the new count to the counter

        for (j = 0; j < looping; j++)   // Loop the card shift times
        {
            EEPROM.write(start + j, EEPROM.read(start + 5 + j));        // Shift the array values to 5 places earlier in the EEPROM
/*
Serial.print("W[");
Serial.print(start+j);
Serial.print("] Value [");
Serial.print(a[j], HEX);
Serial.print("] \n");
*/
        }
        for (int k = 0; k < 5; k++)     //Shifting loop
        {
            EEPROM.write(start + j + k, 0);
        }
        successDelete();

    }
}

// Find the slot number of the id to be deleted
int findIDSLOT(byte find[])
{
    int count = EEPROM.read(CARD_DB); // Read the first Byte of EEPROM that
    for (int i = 1; i <= count; i++)    // Loop once for each EEPROM entry
    {
        readID(i);              // Read an ID from EEPROM, it is stored in storedCard[6]
        if (memcmp(find, storedCard, sizeof(storedCard)) == 0) // Check to see if the storedCard read from EEPROM 
        {                       // is the same as the find[] ID card passed
            return i;           // The slot number of the card
            break;              // Stop looking we found it
        }
    }
}


// Looks in the EEPROM to try to match any of the EEPROM ID's with the passed ID
boolean findID(byte find[])
{
    int count = EEPROM.read(CARD_DB); // Read the first Byte of EEPROM that
    for (int i = 1; i <= count; i++)    // Loop once for each EEPROM entry
    {
        readID(i);              // Read an ID from EEPROM, it is stored in storedCard[6]
        if (memcmp(find, storedCard, sizeof(storedCard)) == 0) // Check to see if the storedCard read from EEPROM 
        {                       // is the same as the find[] ID card passed
            return true;
        }
    }
    return false;
}

// Opens door and turns on the green LED for setDelay seconds
void openDoor(int setDelay)
{
    setDelay *= 1500;           // Sets delay in seconds
    Serial.end();
    digitalWrite(config.powerPin, LOW);        // Turn off blue LED
    digitalWrite(config.failPin, LOW); // Turn off red LED
    digitalWrite(config.passPin, HIGH);        // Turn on green LED
    digitalWrite(config.doorPin, HIGH);        // Unlock door!

    delay(setDelay);            // Hold door lock open for 2 seconds

    digitalWrite(config.doorPin, LOW); // Relock door

    delay(setDelay);            // Hold green LED om for 2 more seconds

    digitalWrite(config.passPin, LOW); // Turn off green LED
    Serial.begin(9600);
}

// Flashes Red LED if failed login
void failed()
{
    Serial.end();
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
// Blink red fail LED 3 times to indicate failed key
    digitalWrite(config.failPin, HIGH);        // Turn on red LED
    delay(1200);
    Serial.begin(9600);
}

// Check to see if the ID passed is the master programing card
boolean isMaster(byte test[])
{
    byte master[5] = { 0x27, 0x00, 0x0d, 0xbf, 0xb7 };
    return memcmp(test, master, sizeof(master)) == 0 ? true : false;
}

// Check to see if the ID passed is the wipe memory card
boolean isWipe(byte test[])
{
    byte master[5] = { 0x27, 0x00, 0x0d, 0xb8, 0x19 };
    return memcmp(test, master, sizeof(master)) == 0 ? true : false;
}

// Check to see if the ID passed is the deletion card
boolean isDelete(byte test[])
{
    byte master[5] = { 0x29, 0x00, 0x94, 0x20, 0x42 };
    return memcmp(test, master, sizeof(master)) == 0 ? true : false;
}

// Controls LED's for Normal mode, Blue on, all others off
void normalModeOn()
{
    digitalWrite(config.powerPin, HIGH);       // Power pin ON and ready to read card
    digitalWrite(config.passPin, LOW); // Make sure Green LED is off
    digitalWrite(config.failPin, LOW); // Make sure Red LED is off
    digitalWrite(config.doorPin, LOW); // Make sure Door is Locked
}

// Controls LED's for program mode, cycles through RGB
void programModeOn()
{
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is o  ff
    digitalWrite(config.failPin, LOW); // Make sure blue LED is off
    digitalWrite(config.passPin, HIGH);        // Make sure green LED is on
    delay(200);
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    digitalWrite(config.failPin, HIGH);        // Make sure blue LED is on
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is on
    digitalWrite(config.failPin, LOW); // Make sure blue LED is off
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
}

// Controls LED's for delete mode, cycles through RB
void deleteModeOn()
{
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    digitalWrite(config.failPin, HIGH);        // Make sure red LED is on
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is on
    digitalWrite(config.failPin, LOW); // Make sure red LED is off
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
}

// Flashes the green LED 3 times to indicate a successful write to EEPROM
void successWrite()
{
    Serial.end();
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    digitalWrite(config.failPin, LOW); // Make sure red LED is off
    digitalWrite(config.passPin, LOW); // Make sure green LED is on
    delay(200);
    digitalWrite(config.passPin, HIGH);        // Make sure green LED is on
    delay(200);
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.passPin, HIGH);        // Make sure green LED is on
    delay(200);
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.passPin, HIGH);        // Make sure green LED is on
    delay(200);
    Serial.begin(9600);
}

// Flashes the red LED 3 times to indicate a failed write to EEPROM
void failedWrite()
{
    Serial.end();
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    digitalWrite(config.failPin, LOW); // Make sure red LED is on
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.failPin, HIGH);        // Make sure red LED is on
    delay(200);
    digitalWrite(config.failPin, LOW); // Make sure red LED is off
    delay(200);
    digitalWrite(config.failPin, HIGH);        // Make sure red LED is on
    delay(200);
    digitalWrite(config.failPin, LOW); // Make sure red LED is off
    delay(200);
    digitalWrite(config.failPin, HIGH);        // Make sure red LED is on
    delay(200);
    Serial.begin(9600);
}

// Flashes the blue LED 3 times to indicate a success delete to EEPROM
void successDelete()
{
    Serial.end();
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    digitalWrite(config.failPin, LOW); // Make sure red LED is off
    digitalWrite(config.passPin, LOW); // Make sure green LED is on
    delay(200);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is off
    delay(200);
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    delay(200);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is off
    delay(200);
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    delay(200);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is off
    delay(200);
    Serial.begin(9600);
}

// Controls LED's for wipe mode, cycles through BG
void wipeModeOn()
{
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off
    digitalWrite(config.failPin, LOW); // Make sure red LED is off
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(50);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is on 
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off 
    digitalWrite(config.passPin, HIGH);        // Make sure green LED is on
    delay(200);
    digitalWrite(config.powerPin, HIGH);       // Make sure blue LED is on 
    digitalWrite(config.passPin, LOW); // Make sure green LED is off
    delay(200);
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off 
    digitalWrite(config.passPin, HIGH);        // Make sure green LED is on
    delay(200);
    digitalWrite(config.powerPin, LOW);        // Make sure blue LED is off 
    digitalWrite(config.passPin, LOW); // Make sure green LED is on
    delay(200);
    digitalWrite(config.failPin, HIGH);        // Make sure green LED is on
}

/*
ATmega168 with Arduino Bootloader $4.95
http://www.sparkfun.com/commerce/product_info.php?products_id=8846

Crystal 16MHz $1.50
http://www.sparkfun.com/commerce/product_info.php?products_id=536

Capacitor Ceramic 22pF $0.25 (x2)
http://www.sparkfun.com/commerce/product_info.php?products_id=8571

Capacitor Ceramic 0.1uF $0.25
http://www.sparkfun.com/commerce/product_info.php?products_id=8375

Resistor 10k Ohm 1/6th Watt PTH $0.25
http://www.sparkfun.com/commerce/product_info.php?products_id=8374

Mini Push Button Switch $0.35
http://www.sparkfun.com/commerce/product_info.php?products_id=97

Triple Output LED RGB - Diffused $1.95
http://www.sparkfun.com/commerce/product_info.php?products_id=9264

RFID Reader ID-12 $29.95
http://www.sparkfun.com/commerce/product_info.php?products_id=8419

RFID Reader ID-20 $34.95
http://www.sparkfun.com/commerce/product_info.php?products_id=8628

RFID Reader Breakout $0.95
http://www.sparkfun.com/commerce/product_info.php?products_id=8423

Break Away Headers - Straight $2.50
http://www.sparkfun.com/commerce/product_info.php?products_id=116

RFID Tag - 125kHz $1.95
http://www.sparkfun.com/commerce/product_info.php?products_id=8310

Door Fail Secure access control Electric Strike v5 NO $17.50 (kawamall, bay)
http://cgi.ebay.com/Door-Fail-Secure-access-control-Electric-Strike-v5-NO_W0QQitemZ350230332833QQcmdZViewItemQQptZLH_DefaultDomain_0?hash=item518b5ac5a1#ht_3787wt_1167
*/
